package com.dao;

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.hibernate.FetchMode;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.ProjectionList;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.sql.JoinType;
import org.springframework.dao.DataAccessException;
import org.springframework.orm.hibernate4.support.HibernateDaoSupport;

import com.common.BaseModel;
import com.model.Stock;
import com.query.BaseQuery;
import com.util.DateUtil;

public abstract class BaseHibernateDAO<T extends BaseModel, Q extends BaseQuery>
		extends HibernateDaoSupport implements BaseModelDAO<T> {

	public abstract Class<T> getModelClass();

	public T find(Long id) throws DataAccessException {
		T object = getHibernateTemplate().get(getModelClass(), id);
		return object;
	}

	@SuppressWarnings({ "unchecked" })
	public T findFULL(Long id) throws DataAccessException {

		DetachedCriteria criteria = DetachedCriteria.forClass(getModelClass())
				.add(Restrictions.eq("id", id));

		for (String listName : getListNames()) {
			criteria.setFetchMode(listName, FetchMode.JOIN);
		}

		T object = (T) getHibernateTemplate().findByCriteria(criteria).get(0);
		return object;
	}

	@SuppressWarnings("unchecked")
	public List<T> getList() throws DataAccessException {
		List<T> objectList = (List<T>) getHibernateTemplate().find(
				"FROM " + getModelClass().getSimpleName());
		return objectList;
	}

	public T save(T object) throws DataAccessException {
		if (object != null) {
			getHibernateTemplate().save(object);
		}
		return object;
	}

	public T edit(T object) throws DataAccessException {
		if (object != null) {
			getHibernateTemplate().merge(object);
		}
		return object;
	}

	@SuppressWarnings("unchecked")
	public List<T> find(String queryString, Object... values)
			throws DataAccessException {
		return (List<T>) getHibernateTemplate().find(queryString, values);
	}

	public void delete(T object) throws DataAccessException {
		getHibernateTemplate().delete(object);
	}

	public void saveList(List<T> objectList) throws DataAccessException {

		for (T object : objectList) {
			save(object);
		}
	}

	public List<T> getList(String nombreOID) {
		// TODO implementar dende sea necesario
		return null;
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public List<T> getList(int first, int pageSize, String sortField,
			boolean sortOrder, Map<String, Object> filters, Boolean startMode)
			throws DataAccessException {
		List<T> objectList = null;

		DetachedCriteria criteria = DetachedCriteria.forClass(getModelClass());

		if (sortField != null && !sortField.isEmpty()) {
			if (sortOrder) {
				criteria.addOrder(Order.asc(sortField));
			} else {
				criteria.addOrder(Order.desc(sortField));
			}
		}

		if (!filters.isEmpty()) {
			Iterator<Entry<String, Object>> iterator = filters.entrySet()
					.iterator();
			List<String> alias = new ArrayList<String>();
			while (iterator.hasNext()) {
				Entry<String, Object> entry = iterator.next();
				String[] temp = entry.getKey().split("\\.");
				if (temp.length > 1) {
					String objeto = temp[0];
					if (!alias.contains(objeto)) {
						criteria.createAlias(objeto, objeto,
								JoinType.LEFT_OUTER_JOIN);
						alias.add(objeto);
					}
				}

				Object object = entry.getValue();
				if (object instanceof Long) {
					try {
						Long numero = Long.parseLong(object.toString());
						criteria.add(Restrictions.eq(entry.getKey(), numero));
					} catch (NumberFormatException e) {
					}
				} else if (object instanceof String && !"".equals(object)) {
					if (startMode) {
						criteria.add(Restrictions.ilike(entry.getKey(),
								object.toString(), MatchMode.START));
					} else {
						criteria.add(Restrictions.ilike(entry.getKey(),
								object.toString(), MatchMode.ANYWHERE));
					}

				} else if (object instanceof Date) {
					Date fecha = (Date) object;
						criteria.add(Restrictions.between(entry.getKey(),
								DateUtil.getFirstTime(fecha),
								DateUtil.getLastTime(fecha)));
				} else if (object instanceof Enum) {
					criteria.add(Restrictions.eq(entry.getKey(), object));
				} else if (object instanceof List) {
					List lista = (List) object;
					if (lista.size() > 0) {
						criteria.add(Restrictions.in(entry.getKey(), lista));
					}
				} else if (object instanceof Criterion) {
					criteria.add((Criterion) object);
			    }
			}
		}

		objectList = (List<T>) getHibernateTemplate().findByCriteria(criteria,
				first, pageSize);

		return objectList;
	}

	@SuppressWarnings("rawtypes")
	public Long count(Map<String, Object> filters, Boolean startMode)
			throws DataAccessException {
		Long count = 0l;

		DetachedCriteria criteria = DetachedCriteria.forClass(getModelClass());
		criteria.setProjection(Projections.rowCount());
		if (!filters.isEmpty()) {
			Iterator<Entry<String, Object>> iterator = filters.entrySet()
					.iterator();
			List<String> alias = new ArrayList<String>();
			while (iterator.hasNext()) {
				Entry<String, Object> entry = iterator.next();
				String[] temp = entry.getKey().split("\\.");
				if (temp.length > 1) {
					String objeto = temp[0];
					if (!alias.contains(objeto)) {
						criteria.createAlias(objeto, objeto,
								JoinType.LEFT_OUTER_JOIN);
						alias.add(objeto);
					}
				}
				Object object = entry.getValue();
				if (object instanceof Long) {
					try {
						Long numero = Long.parseLong(object.toString());
						criteria.add(Restrictions.eq(entry.getKey(), numero));
					} catch (NumberFormatException e) {
					}
				} else if (object instanceof String && !"".equals(object)) {
					if (startMode) {
						criteria.add(Restrictions.ilike(entry.getKey(),
								object.toString(), MatchMode.START));
					} else {
						criteria.add(Restrictions.ilike(entry.getKey(),
								object.toString(), MatchMode.ANYWHERE));
					}
				} else if (object instanceof Date) {
					Date fecha = (Date) object;
					criteria.add(Restrictions.between(entry.getKey(),
							DateUtil.getFirstTime(fecha),
							DateUtil.getLastTime(fecha)));
				} else if (object instanceof Enum) {
					criteria.add(Restrictions.eq(entry.getKey(), object));
				} else if (object instanceof List) {
					List lista = (List) object;
					if (lista.size() > 0) {
						criteria.add(Restrictions.in(entry.getKey(), lista));
					}
				} else if (object instanceof Criterion) {
					criteria.add((Criterion) object);
			    }
				
			}
		}
		count = (Long) getHibernateTemplate().findByCriteria(criteria).get(0);

		return count;
	}
	
	@SuppressWarnings("rawtypes")
	public List sum(Map<String, Object> filters, Boolean startMode, String... properties)
			throws DataAccessException {
		List listaValores = new ArrayList<>();
		
		ProjectionList projList = Projections.projectionList();

		DetachedCriteria criteria = DetachedCriteria.forClass(getModelClass());
		for(String property : properties){
			projList.add(Projections.sum(property));
		}
		
		criteria.setProjection(projList);
		
		if (!filters.isEmpty()) {
			Iterator<Entry<String, Object>> iterator = filters.entrySet()
					.iterator();
			List<String> alias = new ArrayList<String>();
			while (iterator.hasNext()) {
				Entry<String, Object> entry = iterator.next();
				String[] temp = entry.getKey().split("\\.");
				if (temp.length > 1) {
					String objeto = temp[0];
					if (!alias.contains(objeto)) {
						criteria.createAlias(objeto, objeto,
								JoinType.LEFT_OUTER_JOIN);
						alias.add(objeto);
					}
				}
				Object object = entry.getValue();
				if (object instanceof Long) {
					try {
						Long numero = Long.parseLong(object.toString());
						criteria.add(Restrictions.eq(entry.getKey(), numero));
					} catch (NumberFormatException e) {
					}
				} else if (object instanceof String && !"".equals(object)) {
					if (startMode) {
						criteria.add(Restrictions.ilike(entry.getKey(),
								object.toString(), MatchMode.START));
					} else {
						criteria.add(Restrictions.ilike(entry.getKey(),
								object.toString(), MatchMode.ANYWHERE));
					}
				} else if (object instanceof Date) {
					Date fecha = (Date) object;
					criteria.add(Restrictions.between(entry.getKey(),
							DateUtil.getFirstTime(fecha),
							DateUtil.getLastTime(fecha)));
				} else if (object instanceof Enum) {
					criteria.add(Restrictions.eq(entry.getKey(), object));
				} else if (object instanceof List) {
					List lista = (List) object;
					if (lista.size() > 0) {
						criteria.add(Restrictions.in(entry.getKey(), lista));
					}
				} else if (object instanceof Criterion) {
					criteria.add((Criterion) object);
			    }
				
			}
		}
		listaValores = (List) getHibernateTemplate().findByCriteria(criteria);

		return listaValores;
	}

	public Long count() throws DataAccessException {
		Long count = 0l;

		DetachedCriteria criteria = DetachedCriteria.forClass(getModelClass());
		criteria.setProjection(Projections.rowCount());
		count = (Long) getHibernateTemplate().findByCriteria(criteria).get(0);

		return count;
	}

	public T get(String query) throws DataAccessException {
		// TODO implementar donde sea necesario
		return null;
	}

	public T get(Object query) throws DataAccessException {
		// TODO implementar donde sea necesario
		return null;
	}

	public List<String> getListNames() {
		return null;
	}

	public List<T> getList(Object query) throws DataAccessException {
		// TODO implementar donde sea necesario
		return null;
	}

	public List<T> getList(Object query, Object query2)
			throws DataAccessException {
		// TODO implementar donde sea necesario
		return null;
	}

	public List<Object[]> getListQuery(Object query) throws DataAccessException {
		// TODO implementar donde sea necesario
		return null;
	}

	public List<Object[]> getList2Query(Object query)
			throws DataAccessException {
		// TODO implementar donde sea necesario
		return null;
	}

	public Long count(Object query) throws DataAccessException {
		// TODO implementar donde sea necesario
		return null;
	}

	public Long count(Object query, Object param) throws DataAccessException {
		// TODO implementar donde sea necesario
		return null;
	}

	public Long count(String query) throws DataAccessException {
		// TODO implementar donde sea necesario
		return null;
	}

	public Long count(String query, Object param) throws DataAccessException {
		// TODO implementar donde sea necesario
		return null;
	}

	public T findInitialize(Long id) throws DataAccessException {
		return null;
	}

	public T findBy(Long id) throws DataAccessException {
		return null;
	}

	public T findBy(Long id, Long id2) throws DataAccessException {
		return null;
	}

	public T findBy(String id) throws DataAccessException {
		return null;
	}

	public T findByCodigo(Long codigo) throws DataAccessException {
		return null;
	}

	@SuppressWarnings("unchecked")
	public T findBy(Long idCliente, Long idProducto, Long idProveedor) throws DataAccessException {
		T object = null;
		DetachedCriteria criteria = DetachedCriteria.forClass(Stock.class);
		
		criteria.add(Restrictions.eq("producto.id", idProducto));
		
		if (idCliente==null){
			criteria.add(Restrictions.isNull("cliente.id"));
		}else{
			criteria.add(Restrictions.eq("cliente.id", idCliente));
		}
		
		try {
			object = (T) getHibernateTemplate().findByCriteria(criteria).get(0);
		} catch (Throwable t) {
		}

		return object;
	}

	public T buscarPor(Long idProducto, Long idProveedor)
			throws DataAccessException {
		return null;
	}

	public boolean existeNumeroDeRemitoDeCliente(Long numeroDeRemito, Long idDeCliente) throws Exception
	{
		return true;
	}
	
	public List getListObjects(Object query) throws DataAccessException {
		// TODO implementar donde sea necesario
		return null;
	}
}
